﻿using System;
using System.Collections.Generic;
using EmbeddedSharp.Core;
using System.IO;

namespace EmbeddedSharp.Scripting {
    public class ScriptingContext : IContext {

        public StackFrame Peep() {
            return Stack.Peek();
        }

        /// <summary>
        /// 
        /// </summary>
        public bool Evaluated {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="fileName"></param>
        public virtual void Run(string fileName) {
            string source = File.ReadAllText(fileName);
            ScriptingObject vm = new ScriptingObject(source, this);
            vm.Run();
        }

        protected IFunctionManager functionManager;

        private ContextParserFactory cpf = null;

        /// <summary>
        /// 
        /// </summary>
        public ContextStack Stack {
            get;
            set;
        }

        /// <summary>
        /// Global scope
        /// </summary>
        public StackFrame RootFrame {
            get;
            set;
        }

        public List<string> InputHistory = new List<string>();

        public List<object> OutputHistory = new List<object>();

        public List<Exception> ErrorHistory = new List<Exception>();

        /// <summary>
        /// Default constructor
        /// </summary>
        public ScriptingContext() {
            RootFrame = new StackFrame();
            Stack = new ContextStack();
            Stack.Push(RootFrame);

            functionManager = new ContextFunctionManager(this);
            cpf = new ContextParserFactory(this);

            DefaultDependencyManager.Instance.FunctionManager = functionManager;
            DefaultDependencyManager.Instance.ParserFactory = cpf;
        }

        /// <summary>
        /// Push a new frame on top of the stack
        /// </summary>
        /// <param name="frame"></param>
        public void Push(StackFrame frame) {
            this.Stack.Push(frame);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public StackFrame Pop() {
            return this.Stack.Pop();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="variableName"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual object AddVariable(
            string variableName, object value) {
            StackFrame frame = this.Stack.Peek();
            frame.Add(variableName, value);

            return value;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="variableName"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual object SetVariable(
            string variableName, object value) {
            StackFrame frame = this.Stack.Peek();
            frame[variableName] = value;

            return value;
        }

        /// <summary>
        /// Parse the expression
        /// </summary>
        /// <param name="expr">the expression</param>
        /// <returns>the tree form of the expression</returns>
        public virtual Node Parse(string expr) {
            ExpressionParser p = this.cpf.CreateParser(expr, this);
            Node root = p.Parse();
            return root;
        }

        /// <summary>
        /// Evaluate the expression
        /// </summary>
        /// <param name="expression">the expression</param>
        /// <returns>the evaluated result</returns>
        public virtual object Evaluate(string expression) {
            Node root = Parse(expression);
            if (root is ContextFuncNode) {
                (root as ContextFuncNode).Context = this;
            }
            object result = root.Evaluate();

            return result;
        }

        /// <summary>
        /// Get variable by name
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public virtual object GetVariable(string variableName) {
            if (
                variableName.Equals(
                    "true",
                    StringComparison.CurrentCultureIgnoreCase)
                ||
                variableName.Equals(
                    "false",
                    StringComparison.CurrentCultureIgnoreCase)) {
                IdentifierNode node
                    = new IdentifierNode(variableName);
                return node;
            }

            //return this[token.ToString()];
            StackFrame frame = this.Stack.Peek();
            if (frame.ContainsKey(variableName)) {
                return frame[variableName];
            }
            if (frame != RootFrame && RootFrame.ContainsKey(variableName)) {
                return RootFrame[variableName];
            }

            return null;
        }

        /// <summary>
        /// Assign the value to the variable
        /// </summary>
        /// <param name="variableName">the variable name</param>
        /// <param name="value">the value</param>
        /// <returns></returns>
        public virtual object Assign(
            string variableName, object value) {
            if (value == null) {
                throw new ArgumentNullException("value");
            }

            if (HasVariable(variableName)) {
                this.SetVariable(variableName, value);
            } else {
                this.AddVariable(variableName, value);
            }

            //Node node = this.GetVariable(variableName);
            //if (value is Node)
            //{
            //    if (node != null)
            //    {
            //        this.SetVariable(variableName, value as Node);
            //    }
            //    else
            //    {
            //        this.AddVariable(variableName, value as Node);
            //    }
            //}
            //else
            //{
            //    if (node != null)
            //    {
            //        node.Value = value.ToString();
            //    }
            //    else
            //    {
            //        if (Utils.IsNumeric(value))
            //        {
            //            node = new NumberNode(
            //                Convert.ToDouble(value));
            //        }
            //        else if (
            //            Utils.IsIdentifier(value.ToString()))
            //        {
            //            node = new IdentifierNode(value);
            //        }
            //        else
            //        {
            //            node = new StringNode(
            //                Convert.ToString(value));
            //        }
            //        this.AddVariable(variableName, node);
            //    }
            //}

            //if (obj is string) {
            //    Variable var = null;
            //    if (this.ContainsKey(obj.ToString())) {
            //        var = (Variable)this[obj.ToString()];
            //    } else {
            //        var = new Variable();
            //        this[obj.ToString()] = var;
            //    }

            //    var.Value = result;
            //} else if (obj is Variable) {
            //    ((Variable)obj).Value = result;
            //} else {
            //    this[obj.ToString()] = result;
            //}

            return value;
        }

        /// <summary>
        /// Remove a variable from the stack
        /// </summary>
        /// <param name="p"></param>
        public virtual void RemoveVariable(string p) {
            StackFrame frame = this.Stack.Pop();
            if (frame.ContainsKey(p)) {
                frame.Remove(p);
            } else {
                if (RootFrame.ContainsKey(p)) {
                    RootFrame.Remove(p);
                }
            }
        }

        /// <summary>
        /// Check if the designated variable exists
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public virtual bool HasVariable(string p) {
            StackFrame frame = this.Stack.Peek();
            return frame.ContainsKey(p) || RootFrame.ContainsKey(p);
        }

        #region IContext Members

        public virtual IExpressionParserFactory GetParserFactory() {
            return this.cpf;
        }

        public virtual IFunctionManager GetFunctionManager() {
            return functionManager;
        }

        public virtual IUnaryFunctionFactory UnaryFunctionFactory {
            get {
                return new DefaultUnaryFunctionFactory(this);
            }
        }

        public virtual void Clear() {
            Stack.Peek().Clear();
        }

        #endregion

        #region IDisposable Members

        public void Dispose() {
            RootFrame = null;
            Stack = null;
        }

        #endregion
    }
}
